<script lang="ts">
import { Teleport, h, nextTick, onMounted, reactive, ref, watch } from 'vue'
import { createPopper } from '@popperjs/core'
import utils from '@/common/utils'

interface PropsType {
    scrollbarWidth: number
    scrollbarDraggable: boolean
    scrollbarOpacity: number
    scrollbarColor: string
}
interface StateType {
    popShown: boolean
    prepareToHide: boolean
    mouseDown: boolean
    mouseIn: boolean
    startScrollTop?: number
    startMouseY?: number
    scacle: number
    popInstance: any
}
export default {
    props: {
        scrollbarWidth: {
            type: Number,
            default: 8,
        },
        scrollbarDraggable: {
            type: Boolean,
            default: true,
        },
        scrollbarOpacity: {
            type: Number,
            default: 0.5,
        },
        scrollbarColor: {
            type: String,
            default: '#666',
        },
    },
    setup(props: PropsType, ctx) {
        const state = reactive<StateType>({
            popShown: false,
            prepareToHide: false,
            mouseDown: false,
            mouseIn: false,
            startScrollTop: undefined,
            startMouseY: undefined,
            scacle: 1,
            popInstance: undefined,
        })

        watch(
            () => state.popShown,
            () => {
                if (state.popShown) {
                    methods.onPopShown()
                } else {
                    methods.onPopHidden()
                }
            },
        )

        onMounted(() => {
            methods.addEventListener()
        })

        const targetRef = ref()
        const popRef = ref()
        const methods = {
            getTargetEl() {
                return utils.getEl(targetRef.value)
            },
            getPopEl() {
                return utils.getEl(popRef.value)
            },
            addEventListener() {
                let targetEl = methods.getTargetEl()!
                targetEl.addEventListener('scroll', methods.onScroll)
                targetEl.addEventListener('mouseover', methods.onMouseover)
                targetEl.addEventListener('mouseleave', methods.onMouseout)
                targetEl.addEventListener('click', methods.onClick)
            },
            addPopEventListener() {
                let popEl = methods.getPopEl()
                if (!popEl) {
                    return
                }
                popEl.addEventListener('mousedown', methods.onMousedown)
                methods.addDocumentEventListener()
                popEl.addEventListener('mouseover', methods.onMouseover)
                popEl.addEventListener('mouseout', methods.onMouseout)
            },
            addDocumentEventListener() {
                window.document.addEventListener('mousemove', methods.onMousemove)
                window.document.addEventListener('mouseup', methods.onMouseup)
            },
            removeDocumentEventListener() {
                window.document.removeEventListener('mousemove', methods.onMousemove)
                window.document.removeEventListener('mouseup', methods.onMouseup)
            },
            onScroll() {
                methods.pinToTarget()
            },
            onMousedown(e: MouseEvent) {
                e.preventDefault()
                state.mouseDown = true
                let targetEl = methods.getTargetEl()!
                state.startScrollTop = targetEl.scrollTop
                state.startMouseY = e.clientY
            },
            onMousemove(e: MouseEvent) {
                if (state.mouseDown) {
                    e.preventDefault()
                    let targetEl = methods.getTargetEl()!
                    let newScrollTop = (e.clientY - state.startMouseY!) / state.scacle + state.startScrollTop!
                    targetEl.scroll(0, newScrollTop)
                }
            },
            onMouseup() {
                state.mouseDown = false
                if (state.mouseIn) {
                    return
                }
                methods.readyToHide()
            },
            onMouseover() {
                state.popShown = true
                state.prepareToHide = false
                state.mouseIn = true
            },
            onMouseout() {
                state.mouseIn = false
                if (state.mouseDown) {
                    return
                }
                methods.readyToHide()
            },
            readyToHide() {
                state.prepareToHide = true
                setTimeout(() => {
                    if (state.prepareToHide) {
                        state.popShown = false
                    }
                }, 50)
            },
            onClick() {
                let targetEl = methods.getTargetEl()
                if (!targetEl) {
                    return
                }
                if (targetEl.clientHeight === targetEl.scrollHeight) {
                    state.popShown = false
                } else {
                    state.popShown = true
                    state.prepareToHide = false
                    methods.pinToTarget()
                }
            },
            onPopShown() {
                nextTick(() => {
                    methods.pinToTarget()
                    if (props.scrollbarDraggable) {
                        methods.addPopEventListener()
                    }
                })
            },
            onPopHidden() {
                if (props.scrollbarDraggable) {
                    methods.removeDocumentEventListener()
                }
            },
            pinToTarget() {
                let popEl = methods.getPopEl()
                if (!popEl) {
                    return
                }

                let targetEl = methods.getTargetEl()!

                state.popInstance?.destroy()
                state.popInstance = createPopper(targetEl, popEl, {
                    strategy: 'fixed',
                    placement: 'right-start',
                    modifiers: [
                        {
                            name: 'updatePopHeight',
                            enabled: true,
                            phase: 'main',
                            fn: opts => {
                                let { reference, popper } = opts.state.elements
                                const referenceEl = reference as HTMLElement
                                let scacle = referenceEl.clientHeight / referenceEl.scrollHeight
                                if (scacle >= 0.99 && scacle <= 1.01) {
                                    state.popShown = false
                                } else {
                                    let popHeight = referenceEl.clientHeight * scacle
                                    popper.style.height = popHeight + 'px'
                                    let offsetY = referenceEl.scrollTop * scacle
                                    opts.state.options.modifiers.find(a => a.name === 'offset').options.offset[0] =
                                        offsetY
                                }
                                state.scacle = scacle
                            },
                        },
                        {
                            name: 'offset',
                            options: {
                                offset: [0, -props.scrollbarWidth],
                            },
                            requires: ['updatePopHeight'],
                        },
                    ],
                })
            },
        }

        ctx.expose({
            getTargetEl: methods.getTargetEl,
            getPopEl: methods.getPopEl,
        })

        return () => {
            let targetAttrs = Object.assign(
                {
                    ref: targetRef,
                    class: 'c-scroll-hide',
                },
                ctx.attrs,
            )
            let elList = [h(ctx.slots.default!()[0], targetAttrs)]
            if (state.popShown || state.mouseDown) {
                let popStyle = {
                    opacity: props.scrollbarOpacity,
                    width: props.scrollbarWidth + 'px',
                    borderRadius: props.scrollbarWidth / 2 + 'px',
                    backgroundColor: props.scrollbarColor,
                }

                let popRender = h('div', {
                    ref: popRef,
                    style: popStyle,
                    class: 'c-scrollbar',
                })

                elList.push(
                    h(
                        Teleport,
                        {
                            to: 'body',
                        },
                        popRender,
                    ),
                )
            }

            return elList
        }
    },
}
</script>
